<template>
  <div class="topo">
    <div class="chat" ref="chatRef"></div>
    <div class="tooltip" id="tooltip" style="display: none" ref="tooltipRef"></div>
    <!-- 图例 -->
    <div class="legend" v-if="legends.length">
      <div class="legend-item" v-for="item in legends">
        <i :class="`legend-item--icon iconfont icon-${item.icon}`" :style="{ color: item.color }"></i>
        <div class="legend-item--name">{{ item.label }}</div>
      </div>
    </div>
  </div>
</template>
<script setup>
import * as d3 from 'd3'

const chatRef = ref(null)
const tooltipRef = ref(null)

const props = defineProps({
  data: { type: Object },
})

let isAttackStage = ref(false)
let tooltip

const legends = computed(() => {
  if (isAttackStage.value) {
    return []
  } else {
    return [
      { label: '进程', color: '#8fb6fd', icon: 'setting' },
      { label: '文件', color: '#e6a23c', icon: 'file' },
      { label: '网络', color: '#9c27b0', icon: 'network' },
    ]
  }
})

onMounted(() => {
  tooltip = d3
    .select('body')
    .append('div')
    .attr('class', 'tooltip')
    .style('position', 'absolute')
    .style('display', 'none')
  drawChart()
})

let svg

function drawChart() {
  if (svg) {
    svg.remove()
    svg = null
  }

  const topologyData = props.data

  // Prepare nodes and links
  const nodes = []
  const links = []

  // 判断是否有
  isAttackStage.value = false

  const getLabel = data => {
    let label = data.comm

    if (data.nodetype == '文件' && data.file_path) {
      let filePaths = []
      label = null

      if (data.file_path.indexOf('/') != -1) {
        filePaths = data.file_path.split('/')
      } else if (data.file_path.indexOf('\\') != -1) {
        filePaths = data.file_path.split('\\')
      }

      while (!label && filePaths.length > 0) {
        label = filePaths.pop()
      }
    } else if (data.nodetype == '网络') {
      label = data.dip + ':' + data.dport
    }
    return label
  }

  for (const key in topologyData.topologyid) {
    const item = topologyData.topologyid[key]
    if (item.attackstage) {
      isAttackStage.value = true
      break
    }
  }

  if (isAttackStage.value) {
    for (const key in topologyData.topologyid) {
      const data = topologyData.topologyid[key]
      const id = key

      if (data.attackstage) {
        nodes.push({
          id: id,
          type: 'attackstage',
          label: getLabel(data),
          data: data,
        })
      } else {
        nodes.push({
          id: id,
          type: 'other',
          label: getLabel(data),
          data: data,
        })
      }
    }
  } else {
    for (const key in topologyData.topologyid) {
      const data = topologyData.topologyid[key]
      const id = key

      nodes.push({
        id: id,
        type: {
          进程: 'process',
          文件: 'file',
          网络: 'network',
        }[data.nodetype],
        label: getLabel(data),
        data: data,
      })
    }
  }

  // Add links
  for (const link of topologyData.related) {
    let type = link.attribute

    const data = topologyData.topologyid[link.end - 1]

    if (link.attackstage) {
      type += '-攻击步骤' + link.attackstage
    }

    links.push({
      source: link.start.toString(),
      target: link.end.toString(),
      type,
    })
  }

  // Create the graph
  const width = chatRef.value.clientWidth
  const height = chatRef.value.clientHeight

  svg = d3.select(chatRef.value).append('svg').attr('width', width).attr('height', height)

  const g = svg.append('g')

  // Create a tooltip div
  // const tooltip = d3.select('#tooltip')

  // Add arrow marker definition first
  svg
    .append('defs')
    .append('marker')
    .attr('id', 'arrowhead')
    .attr('viewBox', '0 0 10 10')
    .attr('refX', 18) // 箭头起始位置
    .attr('refY', 5) // 箭头中心位置
    .attr('markerWidth', 8)
    .attr('markerHeight', 8)
    .attr('orient', 'auto')
    .append('path')
    .attr('d', 'M 0,0 L 10,5 L 0,10 z')
    .attr('fill', '#D6DBDF')

  svg
    .append('defs')
    .append('marker')
    .attr('id', 'arrowhead-attack')
    .attr('viewBox', '0 0 10 10')
    .attr('refX', 20) // 箭头起始位置
    .attr('refY', 5) // 箭头中心位置
    .attr('markerWidth', 8)
    .attr('markerHeight', 8)
    .attr('orient', 'auto')
    .append('path')
    .attr('d', 'M 0,0 L 10,5 L 0,10 z')
    .attr('fill', '#E74C3C')

  // Create a simulation with forces for circular layout
  const simulation = d3
    .forceSimulation(nodes)
    .force(
      'link',
      d3
        .forceLink(links)
        .id(d => d.id)
        .distance(150),
    )
    .force('charge', d3.forceManyBody().strength(-300))
    .force('center', d3.forceCenter(width / 2, height / 2))
    .force('collision', d3.forceCollide().radius(50))
    // Add radial force for circular layout
    .force('radial', d3.forceRadial(width / 3, width / 2, height / 2).strength(0.2))

  // Draw links as straight lines with arrows
  const link = g
    .append('g')
    .selectAll('line')
    .data(links)
    .enter()
    .append('line')
    .attr('class', 'edgePath')
    .attr('stroke-width', d => {
      if (d.type.indexOf('攻击步骤') != -1) return 2
      return 1
    })

    .attr('stroke', d => {
      if (d.type.indexOf('攻击步骤') != -1) return '#E74C3C' // 红色
      return '#D6DBDF' // 默认颜色
    })

    // .attr('marker-end', 'url(#arrowhead)')
    .attr('marker-end', d => {
      return d.type.indexOf('攻击步骤') != -1 ? 'url(#arrowhead-attack)' : 'url(#arrowhead)'
    })

  // Add link labels
  const linkLabels = g
    .append('g')
    .selectAll('text')
    .data(links)
    .enter()
    .append('text')
    .attr('class', 'link-label')
    .text(d => d.type)
    .attr('font-size', 10)
    .attr('dy', -5)

  // Create node groups
  const node = g
    .append('g')
    .selectAll('.node')
    .data(nodes)
    .enter()
    .append('g')
    .attr('class', d => `node ${d.type}`)
    .call(d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended))

  // Add foreignObject with icon for each node
  node
    .append('foreignObject')
    .attr('x', -16)
    .attr('y', -16)
    .attr('width', 32)
    .attr('height', 32)
    // .style('background', '#fff')
    // .html(
    //   `<div style="width:100%;height:100%;display:flex;justify-content:center;align-items:center;"><i class="icon iconfont icon-analysis" style="display:block;"></i></div>`,
    // )
    .html(d => {
      let icon = 'icon-setting'

      if (d.data.nodetype == '文件') icon = 'icon-file'
      else if (d.data.nodetype == '网络') icon = 'icon-network'
      else if (d.data.nodetype == '进程') icon = 'icon-setting'

      return `<div class="icon"><i class="iconfont ${icon}" style="display:block;"></i></div>`
    })
    .on('mouseover', showTooltip)
    .on('mouseout', hideTooltip)

  // Add text labels below nodes
  const nodeLabels = node
    .append('text')
    .attr('class', 'node-label')
    .attr('dy', 30)
    .attr('text-anchor', 'middle')
    .text(d => {
      // Truncate long labels
      if (d.label.length > 15) {
        return d.label.substring(0, 15) + '...'
      }
      return d.label
    })

  // Update positions on simulation tick
  simulation.on('tick', () => {
    // Keep nodes within the bounds of the SVG
    nodes.forEach(d => {
      d.x = Math.max(50, Math.min(width - 50, d.x))
      d.y = Math.max(50, Math.min(height - 50, d.y))
    })

    // Update link positions as straight lines
    link
      .attr('x1', d => d.source.x)
      .attr('y1', d => d.source.y)
      .attr('x2', d => d.target.x)
      .attr('y2', d => d.target.y)

    // Update link labels
    linkLabels.attr('transform', d => {
      const x = (d.source.x + d.target.x) / 2
      const y = (d.source.y + d.target.y) / 2
      return `translate(${x},${y})`
    })

    // Update node positions
    node.attr('transform', d => `translate(${d.x},${d.y})`)
  })

  // Drag functions
  function dragstarted(event, d) {
    if (!event.active) simulation.alphaTarget(0.3).restart()
    d.fx = d.x
    d.fy = d.y
  }

  function dragged(event, d) {
    d.fx = event.x
    d.fy = event.y
  }

  function dragended(event, d) {
    if (!event.active) simulation.alphaTarget(0)
    d.fx = null
    d.fy = null
  }

  // Tooltip functions
  function showTooltip(event, d) {
    let tooltipContent = `<strong>${d.label}</strong><br>`
    for (const [key, value] of Object.entries(d.data)) {
      if (value && key !== 'label') {
        tooltipContent += `<strong>${key}:</strong> ${value}<br>`
      }
    }

    tooltip
      .html(tooltipContent)
      .style('left', event.pageX + 15 + 'px')
      .style('top', event.pageY + 15 + 'px')
      .style('display', 'block')
  }

  function hideTooltip() {
    tooltip.style('display', 'none')
  }

  // Handle window resize
  window.addEventListener('resize', () => {
    const width = document.body.clientWidth
    const height = document.querySelector('#graph').clientHeight

    svg.attr('width', width).attr('height', height)
    simulation.force('center', d3.forceCenter(width / 2, height / 2))
    simulation.force('radial', d3.forceRadial(width / 3, width / 2, height / 2).strength(0.2))
    simulation.restart()
  })

  // Zoom and pan functionality
  const zoom = d3
    .zoom()
    .scaleExtent([0.5, 3])
    .on('zoom', event => {
      g.attr('transform', event.transform)
    })

  svg.call(zoom)
}

watch(
  () => props.data,
  () => {
    if (props.data) drawChart()
  },
  {
    deep: true,
  },
)
</script>
<style lang="scss" scoped>
.topo {
  height: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
  .chat {
    height: 100%;
    flex: 1;
    position: relative;
  }
}

.legend {
  position: absolute;
  right: 0;
  bottom: 0;
}

.legend-item {
  border: 1px solid #f0f0f0;
  display: flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: 3px;
  .legend-item--dot {
    width: 16px;
    height: 16px;
    border-radius: 100%;
    margin-right: 12px;
  }
  .legend-item--icon {
    font-size: 16px;
    margin-right: 8px;
  }
}
</style>

<style lang="scss">
.node circle {
  stroke: #333;
  stroke-width: 1.5px;
  fill: #fff;
}

.node.process {
  circle {
    fill: rgba($color: #8fb6fd, $alpha: 0.1);
    stroke: #8fb6fd;
  }
  text {
    // fill: #8fb6fd;
  }
  .iconfont {
    color: #8fb6fd;
  }
}

.node.file {
  circle {
    fill: rgba($color: #e6a23c, $alpha: 0.1);
    stroke: #e6a23c;
  }
  text {
    fill: #e6a23c;
  }
  .iconfont {
    color: #e6a23c;
  }
}

.node.network {
  circle {
    fill: rgba($color: #9c27b0, $alpha: 0.1);
    stroke: #9c27b0;
  }
  text {
    fill: #9c27b0;
  }
  .iconfont {
    color: #9c27b0;
  }
}

.node.attackstage {
  circle {
    fill: rgba($color: #f56c6c, $alpha: 0.1);
    stroke: #f56c6c;
  }
  text {
    fill: #f56c6c;
  }

  .iconfont {
    color: #f56c6c;
  }
}

.node.other {
  circle {
    fill: rgba($color: #606266, $alpha: 0.1);
    stroke: #606266;
  }
  text {
    fill: #333;
  }
  .iconfont {
    color: rgba($color: #606266, $alpha: 0.1);
  }
}

.edgePath path {
  stroke: #333;
  stroke-width: 1.5px;
  fill: none;
}

.node text {
  font-size: 12px;
}

.tooltip {
  position: absolute;
  padding: 8px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 4px;
  pointer-events: none;
  font-size: 12px;
  max-width: 300px;
  z-index: 66666666;
}

.topo .icon {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #fff;
  i {
    font-size: 32px;
  }
}
</style>
